mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
fix(cli): honor inherited tasks options (#117528)
* fix(cli): honor inherited tasks options * test(cli): classify task flow JSON support
This commit is contained in:
committed by
GitHub
parent
3bfc9bc804
commit
bfc6bd3da5
@@ -2481,7 +2481,7 @@ src/cli/program/register.configure.ts 1
|
||||
src/cli/program/register.migrate.ts 12
|
||||
src/cli/program/register.onboard.ts 37
|
||||
src/cli/program/register.setup.ts 10
|
||||
src/cli/program/register.status-health-sessions.ts 41
|
||||
src/cli/program/register.status-health-sessions.ts 29
|
||||
src/cli/program/route-specs.ts 1
|
||||
src/cli/run-main.ts 1
|
||||
src/cli/sandbox-cli.ts 4
|
||||
|
||||
@@ -499,7 +499,6 @@ src/cli/plugins-cli-test-helpers.ts
|
||||
src/cli/plugins-cli.install.test.ts
|
||||
src/cli/plugins-cli.runtime.ts
|
||||
src/cli/plugins-cli.update.test.ts
|
||||
src/cli/program/register.status-health-sessions.ts
|
||||
src/cli/run-main.exit.test.ts
|
||||
src/cli/run-main.ts
|
||||
src/cli/skills-cli.commands.test.ts
|
||||
|
||||
@@ -14,15 +14,6 @@ const mocks = vi.hoisted(() => ({
|
||||
sessionsArchiveCommand: vi.fn(),
|
||||
sessionsDeleteCommand: vi.fn(),
|
||||
exportTrajectoryCommand: vi.fn(),
|
||||
tasksListCommand: vi.fn(),
|
||||
tasksAuditCommand: vi.fn(),
|
||||
tasksMaintenanceCommand: vi.fn(),
|
||||
tasksShowCommand: vi.fn(),
|
||||
tasksNotifyCommand: vi.fn(),
|
||||
tasksCancelCommand: vi.fn(),
|
||||
flowsListCommand: vi.fn(),
|
||||
flowsShowCommand: vi.fn(),
|
||||
flowsCancelCommand: vi.fn(),
|
||||
setVerbose: vi.fn(),
|
||||
runtime: {
|
||||
log: vi.fn(),
|
||||
@@ -40,15 +31,6 @@ const sessionsCompactCommand = mocks.sessionsCompactCommand;
|
||||
const sessionsArchiveCommand = mocks.sessionsArchiveCommand;
|
||||
const sessionsDeleteCommand = mocks.sessionsDeleteCommand;
|
||||
const exportTrajectoryCommand = mocks.exportTrajectoryCommand;
|
||||
const tasksListCommand = mocks.tasksListCommand;
|
||||
const tasksAuditCommand = mocks.tasksAuditCommand;
|
||||
const tasksMaintenanceCommand = mocks.tasksMaintenanceCommand;
|
||||
const tasksShowCommand = mocks.tasksShowCommand;
|
||||
const tasksNotifyCommand = mocks.tasksNotifyCommand;
|
||||
const tasksCancelCommand = mocks.tasksCancelCommand;
|
||||
const flowsListCommand = mocks.flowsListCommand;
|
||||
const flowsShowCommand = mocks.flowsShowCommand;
|
||||
const flowsCancelCommand = mocks.flowsCancelCommand;
|
||||
const setVerbose = mocks.setVerbose;
|
||||
const runtime = mocks.runtime;
|
||||
|
||||
@@ -106,21 +88,6 @@ vi.mock("../../commands/export-trajectory.js", () => ({
|
||||
exportTrajectoryCommand: mocks.exportTrajectoryCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/tasks.js", () => ({
|
||||
tasksListCommand: mocks.tasksListCommand,
|
||||
tasksAuditCommand: mocks.tasksAuditCommand,
|
||||
tasksMaintenanceCommand: mocks.tasksMaintenanceCommand,
|
||||
tasksShowCommand: mocks.tasksShowCommand,
|
||||
tasksNotifyCommand: mocks.tasksNotifyCommand,
|
||||
tasksCancelCommand: mocks.tasksCancelCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/flows.js", () => ({
|
||||
flowsListCommand: mocks.flowsListCommand,
|
||||
flowsShowCommand: mocks.flowsShowCommand,
|
||||
flowsCancelCommand: mocks.flowsCancelCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../globals.js", () => ({
|
||||
setVerbose: mocks.setVerbose,
|
||||
}));
|
||||
@@ -152,15 +119,6 @@ describe("registerStatusHealthSessionsCommands", () => {
|
||||
sessionsArchiveCommand.mockResolvedValue(undefined);
|
||||
sessionsDeleteCommand.mockResolvedValue(undefined);
|
||||
exportTrajectoryCommand.mockResolvedValue(undefined);
|
||||
tasksListCommand.mockResolvedValue(undefined);
|
||||
tasksAuditCommand.mockResolvedValue(undefined);
|
||||
tasksMaintenanceCommand.mockResolvedValue(undefined);
|
||||
tasksShowCommand.mockResolvedValue(undefined);
|
||||
tasksNotifyCommand.mockResolvedValue(undefined);
|
||||
tasksCancelCommand.mockResolvedValue(undefined);
|
||||
flowsListCommand.mockResolvedValue(undefined);
|
||||
flowsShowCommand.mockResolvedValue(undefined);
|
||||
flowsCancelCommand.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("runs status command with timeout and debug-derived verbose", async () => {
|
||||
@@ -605,102 +563,4 @@ describe("registerStatusHealthSessionsCommands", () => {
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(exportTrajectoryCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs tasks list from the parent command", async () => {
|
||||
await runCli(["tasks", "--json", "--runtime", "acp", "--status", "running"]);
|
||||
|
||||
expectCommandOptions(tasksListCommand, {
|
||||
json: true,
|
||||
runtime: "acp",
|
||||
status: "running",
|
||||
});
|
||||
});
|
||||
|
||||
it("runs tasks show subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "show", "run-123", "--json"]);
|
||||
|
||||
expectCommandOptions(tasksShowCommand, {
|
||||
lookup: "run-123",
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("runs tasks maintenance subcommand with apply forwarding", async () => {
|
||||
await runCli(["tasks", "--json", "maintenance", "--apply"]);
|
||||
|
||||
expectCommandOptions(tasksMaintenanceCommand, {
|
||||
json: true,
|
||||
apply: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("runs tasks audit subcommand with filters", async () => {
|
||||
await runCli([
|
||||
"tasks",
|
||||
"--json",
|
||||
"audit",
|
||||
"--severity",
|
||||
"error",
|
||||
"--code",
|
||||
"stale_running",
|
||||
"--limit",
|
||||
"5",
|
||||
]);
|
||||
|
||||
expectCommandOptions(tasksAuditCommand, {
|
||||
json: true,
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
limit: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects partially numeric tasks audit limits", async () => {
|
||||
await runCli(["tasks", "--json", "audit", "--limit", "5abc"]);
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
"--limit must be a positive integer, for example --limit 25.",
|
||||
);
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(tasksAuditCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("routes tasks flow commands through the TaskFlow handlers", async () => {
|
||||
await runCli(["tasks", "flow", "list", "--json", "--status", "blocked"]);
|
||||
expectCommandOptions(flowsListCommand, {});
|
||||
|
||||
await runCli(["tasks", "flow", "show", "flow-123", "--json"]);
|
||||
expectCommandOptions(flowsShowCommand, {
|
||||
lookup: "flow-123",
|
||||
});
|
||||
|
||||
await runCli(["tasks", "flow", "cancel", "flow-123"]);
|
||||
expectCommandOptions(flowsCancelCommand, {
|
||||
lookup: "flow-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("runs tasks notify subcommand with lookup and policy forwarding", async () => {
|
||||
await runCli(["tasks", "notify", "run-123", "state_changes"]);
|
||||
|
||||
expectCommandOptions(tasksNotifyCommand, {
|
||||
lookup: "run-123",
|
||||
notify: "state_changes",
|
||||
});
|
||||
});
|
||||
|
||||
it("runs tasks cancel subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "cancel", "run-123"]);
|
||||
|
||||
expectCommandOptions(tasksCancelCommand, {
|
||||
lookup: "run-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not register the legacy top-level flows command", () => {
|
||||
const program = new Command();
|
||||
registerStatusHealthSessionsCommands(program);
|
||||
|
||||
expect(program.commands.find((command) => command.name() === "flows")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,16 +5,9 @@ import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { TASK_FLOW_STATUSES } from "../../tasks/task-flow-registry.types.js";
|
||||
import { TASK_RUNTIMES, TASK_STATUSES } from "../../tasks/task-registry.types.js";
|
||||
import {
|
||||
TASK_SYSTEM_AUDIT_CODES,
|
||||
TASK_SYSTEM_AUDIT_SEVERITIES,
|
||||
type TaskSystemAuditCode,
|
||||
type TaskSystemAuditSeverity,
|
||||
} from "../../tasks/task-system-audit.types.js";
|
||||
import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
import { formatHelpExamples } from "../help-format.js";
|
||||
import { registerTasksCommand } from "./register.tasks.js";
|
||||
|
||||
function resolveVerbose(opts: { verbose?: boolean; debug?: boolean }): boolean {
|
||||
return Boolean(opts.verbose || opts.debug);
|
||||
@@ -63,14 +56,6 @@ function rejectUnsupportedSessionsParentOptions(
|
||||
return true;
|
||||
}
|
||||
|
||||
function createModuleLoader<T>(load: () => Promise<T>): () => Promise<T> {
|
||||
let promise: Promise<T> | undefined;
|
||||
return () => (promise ??= load());
|
||||
}
|
||||
|
||||
const loadTasksCommands = createModuleLoader(() => import("../../commands/tasks.js"));
|
||||
const loadFlowsCommands = createModuleLoader(() => import("../../commands/flows.js"));
|
||||
|
||||
function addSessionsListOptions(command: Command): Command {
|
||||
return command
|
||||
.option("--json", "Output as JSON", false)
|
||||
@@ -225,16 +210,6 @@ function parseTimeoutMs(timeout: unknown): number | null | undefined {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseTasksAuditLimit(limit: unknown): number | null | undefined {
|
||||
const parsed = parseStrictPositiveInteger(limit);
|
||||
if (limit !== undefined && parsed === undefined) {
|
||||
defaultRuntime.error("--limit must be a positive integer, for example --limit 25.");
|
||||
defaultRuntime.exit(1);
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function runWithVerboseAndTimeout(
|
||||
opts: { verbose?: boolean; debug?: boolean; timeout?: unknown },
|
||||
action: (params: { verbose: boolean; timeoutMs: number | undefined }) => Promise<void>,
|
||||
@@ -595,227 +570,5 @@ export function registerStatusHealthSessionsCommands(program: Command) {
|
||||
});
|
||||
});
|
||||
|
||||
const tasksCmd = program
|
||||
.command("tasks")
|
||||
.description("Inspect durable background tasks and TaskFlow state")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", `Filter by kind (${TASK_RUNTIMES.join(", ")})`)
|
||||
.option("--status <name>", `Filter by status (${TASK_STATUSES.join(", ")})`)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksListCommand } = await loadTasksCommands();
|
||||
await tasksListCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
runtime: opts.runtime as string | undefined,
|
||||
status: opts.status as string | undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
tasksCmd.enablePositionalOptions();
|
||||
|
||||
tasksCmd
|
||||
.command("list")
|
||||
.description("List tracked background tasks")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", `Filter by kind (${TASK_RUNTIMES.join(", ")})`)
|
||||
.option("--status <name>", `Filter by status (${TASK_STATUSES.join(", ")})`)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as
|
||||
| {
|
||||
json?: boolean;
|
||||
runtime?: string;
|
||||
status?: string;
|
||||
}
|
||||
| undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksListCommand } = await loadTasksCommands();
|
||||
await tasksListCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
runtime: (opts.runtime as string | undefined) ?? parentOpts?.runtime,
|
||||
status: (opts.status as string | undefined) ?? parentOpts?.status,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("audit")
|
||||
.description("Show stale or broken background tasks and TaskFlows")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--severity <level>", `Filter by severity (${TASK_SYSTEM_AUDIT_SEVERITIES.join(", ")})`)
|
||||
.option("--code <name>", `Filter by finding code (${TASK_SYSTEM_AUDIT_CODES.join(", ")})`)
|
||||
.option("--limit <n>", "Limit displayed findings")
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
const limit = parseTasksAuditLimit(opts.limit);
|
||||
if (limit === null) {
|
||||
return;
|
||||
}
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksAuditCommand } = await loadTasksCommands();
|
||||
await tasksAuditCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
severity: opts.severity as TaskSystemAuditSeverity | undefined,
|
||||
code: opts.code as TaskSystemAuditCode | undefined,
|
||||
limit,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("maintenance")
|
||||
.description("Preview or apply tasks and TaskFlow maintenance")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--apply", "Apply reconciliation, cleanup stamping, and pruning", false)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksMaintenanceCommand } = await loadTasksCommands();
|
||||
await tasksMaintenanceCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
apply: Boolean(opts.apply),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("show")
|
||||
.description("Show one background task by task id, run id, or session key")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksShowCommand } = await loadTasksCommands();
|
||||
await tasksShowCommand(
|
||||
{
|
||||
lookup,
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("notify")
|
||||
.description("Set task notify policy")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.argument("<notify>", "Notify policy (done_only, state_changes, silent)")
|
||||
.action(async (lookup, notify) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksNotifyCommand } = await loadTasksCommands();
|
||||
await tasksNotifyCommand(
|
||||
{
|
||||
lookup,
|
||||
notify: notify as "done_only" | "state_changes" | "silent",
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("cancel")
|
||||
.description("Cancel a running background task")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.action(async (lookup) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksCancelCommand } = await loadTasksCommands();
|
||||
await tasksCancelCommand(
|
||||
{
|
||||
lookup,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("retry <lookups...>")
|
||||
.description("Retry delivery for up to 10 blocked subagent completions")
|
||||
.action(async (lookups: string[]) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksRetryCommand } = await loadTasksCommands();
|
||||
await tasksRetryCommand({ lookups }, defaultRuntime);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("dismiss <lookups...>")
|
||||
.description("Dismiss delivery for up to 10 blocked subagent completions")
|
||||
.action(async (lookups: string[]) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { tasksDismissCommand } = await loadTasksCommands();
|
||||
await tasksDismissCommand({ lookups }, defaultRuntime);
|
||||
});
|
||||
});
|
||||
|
||||
const tasksFlowCmd = tasksCmd
|
||||
.command("flow")
|
||||
.description("Inspect durable TaskFlow state under tasks");
|
||||
|
||||
tasksFlowCmd
|
||||
.command("list")
|
||||
.description("List tracked TaskFlows")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--status <name>", `Filter by status (${TASK_FLOW_STATUSES.join(", ")})`)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { flowsListCommand } = await loadFlowsCommands();
|
||||
await flowsListCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
status: opts.status as string | undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksFlowCmd
|
||||
.command("show")
|
||||
.description("Show one TaskFlow by flow id or owner key")
|
||||
.argument("<lookup>", "Flow id or owner key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { flowsShowCommand } = await loadFlowsCommands();
|
||||
await flowsShowCommand(
|
||||
{
|
||||
lookup,
|
||||
json: Boolean(opts.json),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksFlowCmd
|
||||
.command("cancel")
|
||||
.description("Cancel a running TaskFlow")
|
||||
.argument("<lookup>", "Flow id or owner key")
|
||||
.action(async (lookup) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const { flowsCancelCommand } = await loadFlowsCommands();
|
||||
await flowsCancelCommand(
|
||||
{
|
||||
lookup,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
registerTasksCommand(program);
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
// Task registration tests exercise the real Commander hierarchy and option sources.
|
||||
import { Command } from "commander";
|
||||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerTasksCommand } from "./register.tasks.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
tasksListCommand: vi.fn(),
|
||||
tasksAuditCommand: vi.fn(),
|
||||
tasksMaintenanceCommand: vi.fn(),
|
||||
tasksShowCommand: vi.fn(),
|
||||
tasksNotifyCommand: vi.fn(),
|
||||
tasksCancelCommand: vi.fn(),
|
||||
tasksRetryCommand: vi.fn(),
|
||||
tasksDismissCommand: vi.fn(),
|
||||
flowsListCommand: vi.fn(),
|
||||
flowsShowCommand: vi.fn(),
|
||||
flowsCancelCommand: vi.fn(),
|
||||
tasksModuleLoaded: vi.fn(),
|
||||
flowsModuleLoaded: vi.fn(),
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/tasks.js", () => {
|
||||
mocks.tasksModuleLoaded();
|
||||
return {
|
||||
tasksListCommand: mocks.tasksListCommand,
|
||||
tasksAuditCommand: mocks.tasksAuditCommand,
|
||||
tasksMaintenanceCommand: mocks.tasksMaintenanceCommand,
|
||||
tasksShowCommand: mocks.tasksShowCommand,
|
||||
tasksNotifyCommand: mocks.tasksNotifyCommand,
|
||||
tasksCancelCommand: mocks.tasksCancelCommand,
|
||||
tasksRetryCommand: mocks.tasksRetryCommand,
|
||||
tasksDismissCommand: mocks.tasksDismissCommand,
|
||||
};
|
||||
});
|
||||
vi.mock("../../commands/flows.js", () => {
|
||||
mocks.flowsModuleLoaded();
|
||||
return {
|
||||
flowsListCommand: mocks.flowsListCommand,
|
||||
flowsShowCommand: mocks.flowsShowCommand,
|
||||
flowsCancelCommand: mocks.flowsCancelCommand,
|
||||
};
|
||||
});
|
||||
vi.mock("../../runtime.js", () => ({ defaultRuntime: mocks.runtime }));
|
||||
|
||||
const ownerHandlers = [
|
||||
mocks.tasksListCommand,
|
||||
mocks.tasksAuditCommand,
|
||||
mocks.tasksMaintenanceCommand,
|
||||
mocks.tasksShowCommand,
|
||||
mocks.tasksNotifyCommand,
|
||||
mocks.tasksCancelCommand,
|
||||
mocks.tasksRetryCommand,
|
||||
mocks.tasksDismissCommand,
|
||||
mocks.flowsListCommand,
|
||||
mocks.flowsShowCommand,
|
||||
mocks.flowsCancelCommand,
|
||||
];
|
||||
const requireRecord = createRequireRecord("object", "expected-label");
|
||||
|
||||
function expectCommandOptions(handler: (typeof ownerHandlers)[number], expected: object) {
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const [options, runtime] = handler.mock.calls[0] ?? [];
|
||||
expect(runtime).toBe(mocks.runtime);
|
||||
expect(requireRecord(options, "command options")).toMatchObject(expected);
|
||||
}
|
||||
|
||||
describe("registerTasksCommand", () => {
|
||||
async function runCli(args: string[]) {
|
||||
const program = new Command().enablePositionalOptions();
|
||||
registerTasksCommand(program);
|
||||
await program.parseAsync(args, { from: "user" });
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.runtime.exit.mockImplementation(() => {});
|
||||
for (const handler of ownerHandlers) {
|
||||
handler.mockResolvedValue(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects inherited mutation options before loading task or flow owners", async () => {
|
||||
await runCli(["tasks", "--json", "cancel", "task-123"]);
|
||||
expect(mocks.tasksModuleLoaded).not.toHaveBeenCalled();
|
||||
|
||||
await runCli(["tasks", "--json", "flow", "cancel", "flow-123"]);
|
||||
expect(mocks.flowsModuleLoaded).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs the bare tasks list with root options", async () => {
|
||||
await runCli(["tasks", "--json", "--runtime", "acp", "--status", "running"]);
|
||||
|
||||
expectCommandOptions(mocks.tasksListCommand, {
|
||||
json: true,
|
||||
runtime: "acp",
|
||||
status: "running",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "task list options before the leaf",
|
||||
args: ["tasks", "--json", "--runtime", "acp", "--status", "running", "list"],
|
||||
handler: mocks.tasksListCommand,
|
||||
expected: { json: true, runtime: "acp", status: "running" },
|
||||
},
|
||||
{
|
||||
label: "task list options after the leaf",
|
||||
args: ["tasks", "list", "--json", "--runtime", "acp", "--status", "running"],
|
||||
handler: mocks.tasksListCommand,
|
||||
expected: { json: true, runtime: "acp", status: "running" },
|
||||
},
|
||||
{
|
||||
label: "task audit JSON before the leaf",
|
||||
args: ["tasks", "--json", "audit"],
|
||||
handler: mocks.tasksAuditCommand,
|
||||
expected: { json: true },
|
||||
},
|
||||
{
|
||||
label: "task audit JSON after the leaf",
|
||||
args: ["tasks", "audit", "--json"],
|
||||
handler: mocks.tasksAuditCommand,
|
||||
expected: { json: true },
|
||||
},
|
||||
{
|
||||
label: "task maintenance JSON before the leaf",
|
||||
args: ["tasks", "--json", "maintenance", "--apply"],
|
||||
handler: mocks.tasksMaintenanceCommand,
|
||||
expected: { json: true, apply: true },
|
||||
},
|
||||
{
|
||||
label: "task maintenance JSON after the leaf",
|
||||
args: ["tasks", "maintenance", "--apply", "--json"],
|
||||
handler: mocks.tasksMaintenanceCommand,
|
||||
expected: { json: true, apply: true },
|
||||
},
|
||||
{
|
||||
label: "task show JSON before the leaf",
|
||||
args: ["tasks", "--json", "show", "run-123"],
|
||||
handler: mocks.tasksShowCommand,
|
||||
expected: { lookup: "run-123", json: true },
|
||||
},
|
||||
{
|
||||
label: "task show JSON after the leaf",
|
||||
args: ["tasks", "show", "run-123", "--json"],
|
||||
handler: mocks.tasksShowCommand,
|
||||
expected: { lookup: "run-123", json: true },
|
||||
},
|
||||
{
|
||||
label: "flow list JSON before flow",
|
||||
args: ["tasks", "--json", "flow", "list"],
|
||||
handler: mocks.flowsListCommand,
|
||||
expected: { json: true, status: undefined },
|
||||
},
|
||||
{
|
||||
label: "flow list JSON before the leaf",
|
||||
args: ["tasks", "flow", "--json", "list"],
|
||||
handler: mocks.flowsListCommand,
|
||||
expected: { json: true, status: undefined },
|
||||
},
|
||||
{
|
||||
label: "flow list JSON after the leaf",
|
||||
args: ["tasks", "flow", "list", "--json"],
|
||||
handler: mocks.flowsListCommand,
|
||||
expected: { json: true, status: undefined },
|
||||
},
|
||||
{
|
||||
label: "flow list status after the leaf",
|
||||
args: ["tasks", "flow", "list", "--status", "blocked"],
|
||||
handler: mocks.flowsListCommand,
|
||||
expected: { json: false, status: "blocked" },
|
||||
},
|
||||
{
|
||||
label: "flow show JSON before flow",
|
||||
args: ["tasks", "--json", "flow", "show", "flow-123"],
|
||||
handler: mocks.flowsShowCommand,
|
||||
expected: { lookup: "flow-123", json: true },
|
||||
},
|
||||
{
|
||||
label: "flow show JSON before the leaf",
|
||||
args: ["tasks", "flow", "--json", "show", "flow-123"],
|
||||
handler: mocks.flowsShowCommand,
|
||||
expected: { lookup: "flow-123", json: true },
|
||||
},
|
||||
{
|
||||
label: "flow show JSON after the leaf",
|
||||
args: ["tasks", "flow", "show", "flow-123", "--json"],
|
||||
handler: mocks.flowsShowCommand,
|
||||
expected: { lookup: "flow-123", json: true },
|
||||
},
|
||||
])("routes $label", async ({ args, handler, expected }) => {
|
||||
await runCli(args);
|
||||
expectCommandOptions(handler, expected);
|
||||
});
|
||||
|
||||
it("runs task audit filters and validates the limit", async () => {
|
||||
await runCli([
|
||||
"tasks",
|
||||
"audit",
|
||||
"--severity",
|
||||
"error",
|
||||
"--code",
|
||||
"stale_running",
|
||||
"--limit",
|
||||
"5",
|
||||
]);
|
||||
expectCommandOptions(mocks.tasksAuditCommand, {
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
limit: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects partially numeric task audit limits before owner action", async () => {
|
||||
await runCli(["tasks", "audit", "--limit", "5abc"]);
|
||||
|
||||
expect(mocks.runtime.error).toHaveBeenCalledWith(
|
||||
"--limit must be a positive integer, for example --limit 25.",
|
||||
);
|
||||
expect(mocks.runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(mocks.tasksAuditCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
args: ["tasks", "audit", "--severity", "fatal"],
|
||||
error: "--severity must be warn or error.",
|
||||
},
|
||||
{
|
||||
args: ["tasks", "audit", "--code", "unknown"],
|
||||
error: expect.stringContaining("--code must be"),
|
||||
},
|
||||
])("narrows invalid audit filters before owner action", async ({ args, error }) => {
|
||||
await runCli(args);
|
||||
|
||||
expect(mocks.runtime.error).toHaveBeenCalledWith(error);
|
||||
expect(mocks.tasksAuditCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const rootOptionArgs = {
|
||||
json: ["--json"],
|
||||
runtime: ["--runtime", "cron"],
|
||||
status: ["--status", "running"],
|
||||
} as const;
|
||||
const directUnsupported = [
|
||||
{ leaf: ["audit"], options: ["runtime", "status"] },
|
||||
{ leaf: ["maintenance", "--apply"], options: ["runtime", "status"] },
|
||||
{ leaf: ["show", "task-123"], options: ["runtime", "status"] },
|
||||
{ leaf: ["notify", "task-123", "silent"], options: ["json", "runtime", "status"] },
|
||||
{ leaf: ["cancel", "task-123"], options: ["json", "runtime", "status"] },
|
||||
{ leaf: ["retry", "task-123"], options: ["json", "runtime", "status"] },
|
||||
{ leaf: ["dismiss", "task-123"], options: ["json", "runtime", "status"] },
|
||||
].flatMap(({ leaf, options }) =>
|
||||
options.map((option) => ({
|
||||
label: `tasks ${leaf[0]} with root --${option}`,
|
||||
args: ["tasks", ...rootOptionArgs[option as keyof typeof rootOptionArgs], ...leaf],
|
||||
flag: `--${option}`,
|
||||
})),
|
||||
);
|
||||
const flowRootUnsupported = [
|
||||
{ leaf: ["list"], options: ["runtime", "status"] },
|
||||
{ leaf: ["show", "flow-123"], options: ["runtime", "status"] },
|
||||
{ leaf: ["cancel", "flow-123"], options: ["json", "runtime", "status"] },
|
||||
].flatMap(({ leaf, options }) =>
|
||||
options.map((option) => ({
|
||||
label: `tasks flow ${leaf[0]} with tasks root --${option}`,
|
||||
args: ["tasks", ...rootOptionArgs[option as keyof typeof rootOptionArgs], "flow", ...leaf],
|
||||
flag: `--${option}`,
|
||||
})),
|
||||
);
|
||||
|
||||
it.each([
|
||||
...directUnsupported,
|
||||
...flowRootUnsupported,
|
||||
{
|
||||
label: "tasks flow cancel with flow parent --json",
|
||||
args: ["tasks", "flow", "--json", "cancel", "flow-123"],
|
||||
flag: "--json",
|
||||
},
|
||||
])("rejects $label before owner action", async ({ args, flag }) => {
|
||||
await runCli(args);
|
||||
|
||||
expect(mocks.runtime.error).toHaveBeenCalledWith(expect.stringContaining(flag));
|
||||
expect(mocks.runtime.exit).toHaveBeenCalledWith(1);
|
||||
for (const handler of ownerHandlers) {
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
args: ["tasks", "--runtime", "cron", "--status", "running", "maintenance", "--apply"],
|
||||
error: "`tasks maintenance` does not support inherited options --runtime, --status.",
|
||||
},
|
||||
{
|
||||
args: ["tasks", "--json", "--runtime", "cron", "cancel", "task-123"],
|
||||
error: "`tasks cancel` does not support inherited options --json, --runtime.",
|
||||
},
|
||||
])("lists only explicitly supplied unsupported flags", async ({ args, error }) => {
|
||||
await runCli(args);
|
||||
|
||||
expect(mocks.runtime.error).toHaveBeenCalledWith(error);
|
||||
for (const handler of ownerHandlers) {
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("forwards notify and cancel arguments without root options", async () => {
|
||||
await runCli(["tasks", "notify", "run-123", "state_changes"]);
|
||||
expectCommandOptions(mocks.tasksNotifyCommand, {
|
||||
lookup: "run-123",
|
||||
notify: "state_changes",
|
||||
});
|
||||
|
||||
vi.clearAllMocks();
|
||||
await runCli(["tasks", "cancel", "run-123"]);
|
||||
expectCommandOptions(mocks.tasksCancelCommand, { lookup: "run-123" });
|
||||
});
|
||||
|
||||
it("rejects an invalid notify policy before owner action", async () => {
|
||||
await runCli(["tasks", "notify", "run-123", "sometimes"]);
|
||||
|
||||
expect(mocks.runtime.error).toHaveBeenCalledWith(
|
||||
"Notify policy must be done_only, state_changes, or silent.",
|
||||
);
|
||||
expect(mocks.tasksNotifyCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not register the legacy top-level flows command", () => {
|
||||
const program = new Command();
|
||||
registerTasksCommand(program);
|
||||
expect(program.commands.find((command) => command.name() === "flows")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
// Background task and TaskFlow command registration.
|
||||
import { parseStrictPositiveInteger } from "@openclaw/normalization-core/number-coercion";
|
||||
import type { Command } from "commander";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { TASK_FLOW_STATUSES } from "../../tasks/task-flow-registry.types.js";
|
||||
import {
|
||||
TASK_RUNTIMES,
|
||||
TASK_STATUSES,
|
||||
type TaskNotifyPolicy,
|
||||
} from "../../tasks/task-registry.types.js";
|
||||
import {
|
||||
TASK_SYSTEM_AUDIT_CODES,
|
||||
TASK_SYSTEM_AUDIT_SEVERITIES,
|
||||
} from "../../tasks/task-system-audit.types.js";
|
||||
import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
import { inheritOptionFromParent } from "../command-options.js";
|
||||
import { parseCliEnumFilter } from "../enum-filter.js";
|
||||
|
||||
type TasksParentOption = "json" | "runtime" | "status";
|
||||
const TASKS_PARENT_OPTIONS = ["json", "runtime", "status"] as const;
|
||||
const TASKS_LEAF_OPTION_SUPPORT = {
|
||||
list: TASKS_PARENT_OPTIONS,
|
||||
audit: ["json"],
|
||||
maintenance: ["json"],
|
||||
show: ["json"],
|
||||
notify: [],
|
||||
cancel: [],
|
||||
retry: [],
|
||||
dismiss: [],
|
||||
"flow list": ["json"],
|
||||
"flow show": ["json"],
|
||||
"flow cancel": [],
|
||||
} satisfies Record<string, readonly TasksParentOption[]>;
|
||||
type TasksLeaf = keyof typeof TASKS_LEAF_OPTION_SUPPORT;
|
||||
|
||||
function createModuleLoader<T>(load: () => Promise<T>): () => Promise<T> {
|
||||
let promise: Promise<T> | undefined;
|
||||
return () => (promise ??= load());
|
||||
}
|
||||
|
||||
const loadTasksCommands = createModuleLoader(() => import("../../commands/tasks.js"));
|
||||
const loadFlowsCommands = createModuleLoader(() => import("../../commands/flows.js"));
|
||||
|
||||
async function runOwner<T>(load: () => Promise<T>, action: (owner: T) => Promise<void>) {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => action(await load()));
|
||||
}
|
||||
|
||||
function addTasksListOptions(command: Command): Command {
|
||||
return command
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", `Filter by kind (${TASK_RUNTIMES.join(", ")})`)
|
||||
.option("--status <name>", `Filter by status (${TASK_STATUSES.join(", ")})`);
|
||||
}
|
||||
|
||||
function isTaskNotifyPolicy(value: unknown): value is TaskNotifyPolicy {
|
||||
return value === "done_only" || value === "state_changes" || value === "silent";
|
||||
}
|
||||
|
||||
function resolveTasksLeafOptions(
|
||||
command: Command,
|
||||
leaf: TasksLeaf,
|
||||
): { json?: boolean; runtime?: string; status?: string } | undefined {
|
||||
const supported: readonly TasksParentOption[] = TASKS_LEAF_OPTION_SUPPORT[leaf];
|
||||
const flags = TASKS_PARENT_OPTIONS.filter(
|
||||
(name) =>
|
||||
!supported.includes(name) && inheritOptionFromParent(command, name, "cli") !== undefined,
|
||||
).map((name) => `--${name}`);
|
||||
if (flags.length > 0) {
|
||||
defaultRuntime.error(
|
||||
`\`tasks ${leaf}\` does not support inherited ${flags.length === 1 ? "option" : "options"} ${flags.join(", ")}.`,
|
||||
);
|
||||
defaultRuntime.exit(1);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolveLocal = (name: TasksParentOption): unknown => {
|
||||
const source = command.getOptionValueSource(name);
|
||||
return source && source !== "default" ? command.getOptionValue(name) : undefined;
|
||||
};
|
||||
const resolve = (name: TasksParentOption): unknown =>
|
||||
resolveLocal(name) ?? inheritOptionFromParent(command, name);
|
||||
const json = resolve("json");
|
||||
const runtime = resolve("runtime");
|
||||
const status = leaf === "flow list" ? resolveLocal("status") : resolve("status");
|
||||
return {
|
||||
json: typeof json === "boolean" ? json : undefined,
|
||||
runtime: typeof runtime === "string" ? runtime : undefined,
|
||||
status: typeof status === "string" ? status : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTasksAuditLimit(limit: unknown): number | null | undefined {
|
||||
const parsed = parseStrictPositiveInteger(limit);
|
||||
if (limit !== undefined && parsed === undefined) {
|
||||
defaultRuntime.error("--limit must be a positive integer, for example --limit 25.");
|
||||
defaultRuntime.exit(1);
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function registerTasksCommand(program: Command): void {
|
||||
const tasksCmd = addTasksListOptions(
|
||||
program.command("tasks").description("Inspect durable background tasks and TaskFlow state"),
|
||||
).action(async (opts) => {
|
||||
await runOwner(loadTasksCommands, ({ tasksListCommand }) =>
|
||||
tasksListCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
runtime: typeof opts.runtime === "string" ? opts.runtime : undefined,
|
||||
status: typeof opts.status === "string" ? opts.status : undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
),
|
||||
);
|
||||
});
|
||||
tasksCmd.enablePositionalOptions();
|
||||
|
||||
addTasksListOptions(tasksCmd.command("list").description("List tracked background tasks")).action(
|
||||
async (_opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "list");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksListCommand }) =>
|
||||
tasksListCommand(
|
||||
{
|
||||
json: Boolean(resolved.json),
|
||||
runtime: resolved.runtime,
|
||||
status: resolved.status,
|
||||
},
|
||||
defaultRuntime,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tasksCmd
|
||||
.command("audit")
|
||||
.description("Show stale or broken background tasks and TaskFlows")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--severity <level>", `Filter by severity (${TASK_SYSTEM_AUDIT_SEVERITIES.join(", ")})`)
|
||||
.option("--code <name>", `Filter by finding code (${TASK_SYSTEM_AUDIT_CODES.join(", ")})`)
|
||||
.option("--limit <n>", "Limit displayed findings")
|
||||
.action(async (opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "audit");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const limit = parseTasksAuditLimit(opts.limit);
|
||||
if (limit === null) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksAuditCommand }) =>
|
||||
tasksAuditCommand(
|
||||
{
|
||||
json: Boolean(resolved.json),
|
||||
severity: parseCliEnumFilter(opts.severity, "--severity", TASK_SYSTEM_AUDIT_SEVERITIES),
|
||||
code: parseCliEnumFilter(opts.code, "--code", TASK_SYSTEM_AUDIT_CODES),
|
||||
limit,
|
||||
},
|
||||
defaultRuntime,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("maintenance")
|
||||
.description("Preview or apply tasks and TaskFlow maintenance")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--apply", "Apply reconciliation, cleanup stamping, and pruning", false)
|
||||
.action(async (opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "maintenance");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksMaintenanceCommand }) =>
|
||||
tasksMaintenanceCommand(
|
||||
{ json: Boolean(resolved.json), apply: Boolean(opts.apply) },
|
||||
defaultRuntime,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("show")
|
||||
.description("Show one background task by task id, run id, or session key")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, _opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "show");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksShowCommand }) =>
|
||||
tasksShowCommand({ lookup, json: Boolean(resolved.json) }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("notify")
|
||||
.description("Set task notify policy")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.argument("<notify>", "Notify policy (done_only, state_changes, silent)")
|
||||
.action(async (lookup, notify, _opts, command) => {
|
||||
if (!resolveTasksLeafOptions(command, "notify")) {
|
||||
return;
|
||||
}
|
||||
if (!isTaskNotifyPolicy(notify)) {
|
||||
defaultRuntime.error("Notify policy must be done_only, state_changes, or silent.");
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksNotifyCommand }) =>
|
||||
tasksNotifyCommand({ lookup, notify }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("cancel")
|
||||
.description("Cancel a running background task")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.action(async (lookup, _opts, command) => {
|
||||
if (!resolveTasksLeafOptions(command, "cancel")) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksCancelCommand }) =>
|
||||
tasksCancelCommand({ lookup }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("retry <lookups...>")
|
||||
.description("Retry delivery for up to 10 blocked subagent completions")
|
||||
.action(async (lookups: string[], _opts, command) => {
|
||||
if (!resolveTasksLeafOptions(command, "retry")) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksRetryCommand }) =>
|
||||
tasksRetryCommand({ lookups }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("dismiss <lookups...>")
|
||||
.description("Dismiss delivery for up to 10 blocked subagent completions")
|
||||
.action(async (lookups: string[], _opts, command) => {
|
||||
if (!resolveTasksLeafOptions(command, "dismiss")) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadTasksCommands, ({ tasksDismissCommand }) =>
|
||||
tasksDismissCommand({ lookups }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
const tasksFlowCmd = tasksCmd
|
||||
.command("flow")
|
||||
.description("Inspect durable TaskFlow state under tasks")
|
||||
.option("--json", "Output as JSON", false);
|
||||
tasksFlowCmd.enablePositionalOptions();
|
||||
|
||||
tasksFlowCmd
|
||||
.command("list")
|
||||
.description("List tracked TaskFlows")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--status <name>", `Filter by status (${TASK_FLOW_STATUSES.join(", ")})`)
|
||||
.action(async (_opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "flow list");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadFlowsCommands, ({ flowsListCommand }) =>
|
||||
flowsListCommand({ json: Boolean(resolved.json), status: resolved.status }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksFlowCmd
|
||||
.command("show")
|
||||
.description("Show one TaskFlow by flow id or owner key")
|
||||
.argument("<lookup>", "Flow id or owner key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, _opts, command) => {
|
||||
const resolved = resolveTasksLeafOptions(command, "flow show");
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadFlowsCommands, ({ flowsShowCommand }) =>
|
||||
flowsShowCommand({ lookup, json: Boolean(resolved.json) }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
|
||||
tasksFlowCmd
|
||||
.command("cancel")
|
||||
.description("Cancel a running TaskFlow")
|
||||
.argument("<lookup>", "Flow id or owner key")
|
||||
.action(async (lookup, _opts, command) => {
|
||||
if (!resolveTasksLeafOptions(command, "flow cancel")) {
|
||||
return;
|
||||
}
|
||||
await runOwner(loadFlowsCommands, ({ flowsCancelCommand }) =>
|
||||
flowsCancelCommand({ lookup }, defaultRuntime),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -94,7 +94,6 @@ const JSON_NOT_APPLICABLE = {
|
||||
"models image-fallbacks",
|
||||
"models auth",
|
||||
"models auth order",
|
||||
"tasks flow",
|
||||
"skills workshop",
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user