mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 13:22:14 +00:00
Gateway: track background task lifecycle (#52518)
Merged via squash.
Prepared head SHA: 7c4554204e
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
@@ -39,6 +39,8 @@ vi.mock("./register.status-health-sessions.js", () => ({
|
||||
program.command("status");
|
||||
program.command("health");
|
||||
program.command("sessions");
|
||||
const tasks = program.command("tasks");
|
||||
tasks.command("show");
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -75,6 +77,7 @@ describe("command-registry", () => {
|
||||
expect(names).toContain("agents");
|
||||
expect(names).toContain("backup");
|
||||
expect(names).toContain("sessions");
|
||||
expect(names).toContain("tasks");
|
||||
expect(names).not.toContain("agent");
|
||||
expect(names).not.toContain("status");
|
||||
expect(names).not.toContain("doctor");
|
||||
@@ -139,6 +142,7 @@ describe("command-registry", () => {
|
||||
expect(names).toContain("status");
|
||||
expect(names).toContain("health");
|
||||
expect(names).toContain("sessions");
|
||||
expect(names).toContain("tasks");
|
||||
});
|
||||
|
||||
it("replaces placeholders when loading a grouped entry by secondary command name", async () => {
|
||||
|
||||
@@ -197,6 +197,11 @@ const coreEntries: CoreCliEntry[] = [
|
||||
description: "List stored conversation sessions",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
{
|
||||
name: "tasks",
|
||||
description: "Inspect durable background task state",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
],
|
||||
register: async ({ program }) => {
|
||||
const mod = await import("./register.status-health-sessions.js");
|
||||
|
||||
@@ -81,6 +81,11 @@ export const CORE_CLI_COMMAND_DESCRIPTORS = [
|
||||
description: "List stored conversation sessions",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
{
|
||||
name: "tasks",
|
||||
description: "Inspect durable background task state",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
] as const satisfies ReadonlyArray<CoreCliCommandDescriptor>;
|
||||
|
||||
export function getCoreCliCommandDescriptors(): ReadonlyArray<CoreCliCommandDescriptor> {
|
||||
|
||||
@@ -6,6 +6,10 @@ const statusCommand = vi.fn();
|
||||
const healthCommand = vi.fn();
|
||||
const sessionsCommand = vi.fn();
|
||||
const sessionsCleanupCommand = vi.fn();
|
||||
const tasksListCommand = vi.fn();
|
||||
const tasksShowCommand = vi.fn();
|
||||
const tasksNotifyCommand = vi.fn();
|
||||
const tasksCancelCommand = vi.fn();
|
||||
const setVerbose = vi.fn();
|
||||
|
||||
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
|
||||
@@ -26,6 +30,13 @@ vi.mock("../../commands/sessions-cleanup.js", () => ({
|
||||
sessionsCleanupCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/tasks.js", () => ({
|
||||
tasksListCommand,
|
||||
tasksShowCommand,
|
||||
tasksNotifyCommand,
|
||||
tasksCancelCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../globals.js", () => ({
|
||||
setVerbose,
|
||||
}));
|
||||
@@ -55,6 +66,10 @@ describe("registerStatusHealthSessionsCommands", () => {
|
||||
healthCommand.mockResolvedValue(undefined);
|
||||
sessionsCommand.mockResolvedValue(undefined);
|
||||
sessionsCleanupCommand.mockResolvedValue(undefined);
|
||||
tasksListCommand.mockResolvedValue(undefined);
|
||||
tasksShowCommand.mockResolvedValue(undefined);
|
||||
tasksNotifyCommand.mockResolvedValue(undefined);
|
||||
tasksCancelCommand.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("runs status command with timeout and debug-derived verbose", async () => {
|
||||
@@ -201,4 +216,52 @@ describe("registerStatusHealthSessionsCommands", () => {
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks list from the parent command", async () => {
|
||||
await runCli(["tasks", "--json", "--runtime", "acp", "--status", "running"]);
|
||||
|
||||
expect(tasksListCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
json: true,
|
||||
runtime: "acp",
|
||||
status: "running",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks show subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "show", "run-123", "--json"]);
|
||||
|
||||
expect(tasksShowCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
json: true,
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks notify subcommand with lookup and policy forwarding", async () => {
|
||||
await runCli(["tasks", "notify", "run-123", "state_changes"]);
|
||||
|
||||
expect(tasksNotifyCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
notify: "state_changes",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks cancel subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "cancel", "run-123"]);
|
||||
|
||||
expect(tasksCancelCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,12 @@ import { healthCommand } from "../../commands/health.js";
|
||||
import { sessionsCleanupCommand } from "../../commands/sessions-cleanup.js";
|
||||
import { sessionsCommand } from "../../commands/sessions.js";
|
||||
import { statusCommand } from "../../commands/status.js";
|
||||
import {
|
||||
tasksCancelCommand,
|
||||
tasksListCommand,
|
||||
tasksNotifyCommand,
|
||||
tasksShowCommand,
|
||||
} from "../../commands/tasks.js";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
@@ -213,4 +219,106 @@ export function registerStatusHealthSessionsCommands(program: Command) {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const tasksCmd = program
|
||||
.command("tasks")
|
||||
.description("Inspect durable background task state")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", "Filter by runtime (subagent, acp, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (accepted, running, done, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
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 runtime (subagent, acp, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (accepted, running, done, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as
|
||||
| {
|
||||
json?: boolean;
|
||||
runtime?: string;
|
||||
status?: string;
|
||||
}
|
||||
| undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
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("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 () => {
|
||||
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 () => {
|
||||
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 () => {
|
||||
await tasksCancelCommand(
|
||||
{
|
||||
lookup,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user