feat(cli): thread list defaults to active threads only
CI / check (pull_request) Successful in 2m52s
CI / check (pull_request) Successful in 2m52s
Closes #147. Changes default behavior of `uwf thread list` to show only active threads (idle + running). Adds `--all` flag to opt into the previous full-list behavior. Explicit `--status` still wins over `--all`. - cmdThreadList gains a `showAll: boolean` parameter (default false) - CLI registers `--all` option and passes it through - Test suite includes new `default behavior (issue #147)` describe block covering 9 scenarios; existing tests updated where they implicitly relied on the old "show everything" behavior - README, cli-reference, and usage-reference updated to document the new default and the `--all` flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,7 @@ bun link packages/cli
|
||||
| `uwf thread start <workflow> -p <prompt>` | Create a thread without executing |
|
||||
| `uwf thread exec <thread-id> [--agent <cmd>] [-c <count>] [--background]` | Execute one or more moderator→agent→extract cycles |
|
||||
| `uwf thread show <thread-id>` | Show thread head pointer |
|
||||
| `uwf thread list [--status <status>] [--after <date>] [--before <date>] [--skip <n>] [--take <n>]` | List threads filtered by status (idle, running, completed, active, or comma-separated), time range (ISO or relative like '7d'), with pagination |
|
||||
| `uwf thread list [--status <status>] [--all] [--after <date>] [--before <date>] [--skip <n>] [--take <n>]` | List threads (defaults to active: idle + running). Use `--all` to include completed/cancelled/suspended, or `--status` to filter explicitly (idle, running, suspended, completed, cancelled, active, or comma-separated). Supports time range and pagination. |
|
||||
| `uwf thread read <thread-id> [--quota N] [--before <hash>] [--start]` | Render thread as readable markdown |
|
||||
|
||||
`thread read`, `step list`, and `step show` work on both active and completed threads.
|
||||
@@ -63,6 +63,8 @@ uwf thread start solve-issue -p "Fix the login redirect bug"
|
||||
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV
|
||||
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV -c 3 --agent uwf-builtin
|
||||
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV --background
|
||||
uwf thread list
|
||||
uwf thread list --all
|
||||
uwf thread list --status running
|
||||
uwf thread list --status active
|
||||
uwf thread list --status idle,completed
|
||||
|
||||
@@ -384,7 +384,7 @@ describe("currentRole field", () => {
|
||||
const _compHead = loadActiveThreads(uwfForIndex.varStore)[compId]!.head;
|
||||
completeThread(uwfForIndex.varStore, compId, "completed");
|
||||
|
||||
const list = await cmdThreadList(storageRoot, null, null, null, 0, 100);
|
||||
const list = await cmdThreadList(storageRoot, null, null, null, 0, 100, true);
|
||||
|
||||
const idleItem = list.find((i) => i.thread === idleId);
|
||||
expect(idleItem).toBeDefined();
|
||||
|
||||
@@ -167,7 +167,7 @@ describe("cmdThreadList status filter", () => {
|
||||
expect(result[0]?.status).toBe("completed");
|
||||
});
|
||||
|
||||
test("should return all threads when no status filter provided", async () => {
|
||||
test("should return only active threads when no filter and no --all", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
@@ -185,8 +185,290 @@ describe("cmdThreadList status filter", () => {
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
|
||||
// Default behavior (issue #147): only active threads (idle + running)
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((r) => r.thread).sort()).toEqual([thread1, thread2].sort());
|
||||
|
||||
// Clean up marker
|
||||
await deleteMarker(tmpDir, thread2);
|
||||
});
|
||||
|
||||
test("should return all threads when --all (showAll=true)", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const thread1 = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
const thread2 = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
const thread3 = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
|
||||
await markThreadRunning(tmpDir, thread2, workflowHash);
|
||||
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const index = loadAllThreads(uwfIdx.varStore);
|
||||
const thread3Head = index[thread3]!.head;
|
||||
if (thread3Head === undefined) throw new Error("thread3 head not found");
|
||||
await completeThread(tmpDir, thread3, workflowHash, thread3Head);
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null, true);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((r) => r.thread).sort()).toEqual([thread1, thread2, thread3].sort());
|
||||
|
||||
// Clean up marker
|
||||
await deleteMarker(tmpDir, thread2);
|
||||
});
|
||||
});
|
||||
|
||||
// ── default behavior tests (issue #147) ───────────────────────────────────────
|
||||
|
||||
describe("cmdThreadList default behavior (issue #147)", () => {
|
||||
test("default returns only idle + running threads", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const threadA = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 4000);
|
||||
const threadB = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
const threadC = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
const threadD = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
|
||||
await markThreadRunning(tmpDir, threadB, workflowHash);
|
||||
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const index = loadAllThreads(uwfIdx.varStore);
|
||||
const threadCHead = index[threadC]!.head;
|
||||
if (threadCHead === undefined) throw new Error("threadC head not found");
|
||||
await completeThread(tmpDir, threadC, workflowHash, threadCHead);
|
||||
|
||||
// Cancel threadD
|
||||
const threadDHead = index[threadD]!.head;
|
||||
if (threadDHead === undefined) throw new Error("threadD head not found");
|
||||
const uwfCancel = await createUwfStore(tmpDir);
|
||||
completeThreadInStore(uwfCancel.varStore, threadD, "cancelled");
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((r) => r.thread).sort()).toEqual([threadA, threadB].sort());
|
||||
|
||||
await deleteMarker(tmpDir, threadB);
|
||||
});
|
||||
|
||||
test("default excludes completed threads", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const idleThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 6000);
|
||||
const completedThreads: ThreadId[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const t = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - (5 - i) * 1000);
|
||||
completedThreads.push(t);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const index = loadAllThreads(uwfIdx.varStore);
|
||||
const head = index[t]!.head;
|
||||
if (head === undefined) throw new Error("head not found");
|
||||
await completeThread(tmpDir, t, workflowHash, head);
|
||||
}
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.thread).toBe(idleThread);
|
||||
});
|
||||
|
||||
test("default excludes cancelled threads", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const runningThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 4000);
|
||||
await markThreadRunning(tmpDir, runningThread, workflowHash);
|
||||
|
||||
const cancelled: ThreadId[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const t = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - (3 - i) * 1000);
|
||||
cancelled.push(t);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
completeThreadInStore(uwfIdx.varStore, t, "cancelled");
|
||||
}
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.thread).toBe(runningThread);
|
||||
|
||||
await deleteMarker(tmpDir, runningThread);
|
||||
});
|
||||
|
||||
test("--all (showAll=true) returns every status", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const idleThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 4000);
|
||||
const runningThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
await markThreadRunning(tmpDir, runningThread, workflowHash);
|
||||
|
||||
const completedThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const ch = idx[completedThread]!.head;
|
||||
if (ch === undefined) throw new Error("completedThread head not found");
|
||||
await completeThread(tmpDir, completedThread, workflowHash, ch);
|
||||
|
||||
const cancelledThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
completeThreadInStore(uwfIdx.varStore, cancelledThread, "cancelled");
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null, true);
|
||||
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result.map((r) => r.thread).sort()).toEqual(
|
||||
[idleThread, runningThread, completedThread, cancelledThread].sort(),
|
||||
);
|
||||
|
||||
await deleteMarker(tmpDir, runningThread);
|
||||
});
|
||||
|
||||
test("explicit --status overrides default (still returns just the filtered statuses)", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const _idleThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
const runningThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
await markThreadRunning(tmpDir, runningThread, workflowHash);
|
||||
|
||||
const completedThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const ch = idx[completedThread]!.head;
|
||||
if (ch === undefined) throw new Error("completedThread head not found");
|
||||
await completeThread(tmpDir, completedThread, workflowHash, ch);
|
||||
|
||||
const result = await cmdThreadList(tmpDir, ["completed"], null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.thread).toBe(completedThread);
|
||||
expect(result[0]?.status).toBe("completed");
|
||||
|
||||
await deleteMarker(tmpDir, runningThread);
|
||||
});
|
||||
|
||||
test("--status active keeps working", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const idleThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
const runningThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
await markThreadRunning(tmpDir, runningThread, workflowHash);
|
||||
|
||||
const completedThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const ch = idx[completedThread]!.head;
|
||||
if (ch === undefined) throw new Error("completedThread head not found");
|
||||
await completeThread(tmpDir, completedThread, workflowHash, ch);
|
||||
|
||||
const result = await cmdThreadList(tmpDir, ["idle", "running"], null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((r) => r.thread).sort()).toEqual([idleThread, runningThread].sort());
|
||||
|
||||
await deleteMarker(tmpDir, runningThread);
|
||||
});
|
||||
|
||||
test("--status + --all — explicit status wins", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const _idleThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 3000);
|
||||
const runningThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 2000);
|
||||
await markThreadRunning(tmpDir, runningThread, workflowHash);
|
||||
|
||||
const completedThread = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - 1000);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const ch = idx[completedThread]!.head;
|
||||
if (ch === undefined) throw new Error("completedThread head not found");
|
||||
await completeThread(tmpDir, completedThread, workflowHash, ch);
|
||||
|
||||
const result = await cmdThreadList(tmpDir, ["completed"], null, null, null, null, true);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.thread).toBe(completedThread);
|
||||
|
||||
await deleteMarker(tmpDir, runningThread);
|
||||
});
|
||||
|
||||
test("default returns empty when no threads", async () => {
|
||||
await makeUwfStore(tmpDir);
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("default + time range filter composes correctly", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
const ts1 = Date.UTC(2026, 4, 20, 0, 0, 0);
|
||||
const ts2 = Date.UTC(2026, 4, 21, 0, 0, 0);
|
||||
const ts3 = Date.UTC(2026, 4, 22, 0, 0, 0);
|
||||
const ts4 = Date.UTC(2026, 4, 23, 0, 0, 0);
|
||||
const ts5 = Date.UTC(2026, 4, 24, 0, 0, 0);
|
||||
|
||||
const _t1 = await createTestThread(uwf, tmpDir, workflowHash, ts1);
|
||||
const t2 = await createTestThread(uwf, tmpDir, workflowHash, ts2);
|
||||
const t3 = await createTestThread(uwf, tmpDir, workflowHash, ts3);
|
||||
const t4 = await createTestThread(uwf, tmpDir, workflowHash, ts4);
|
||||
const _t5 = await createTestThread(uwf, tmpDir, workflowHash, ts5);
|
||||
|
||||
// Mark t3 running
|
||||
await markThreadRunning(tmpDir, t3, workflowHash);
|
||||
|
||||
// Complete t4 (should be excluded by default)
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const t4head = idx[t4]!.head;
|
||||
if (t4head === undefined) throw new Error("t4 head not found");
|
||||
await completeThread(tmpDir, t4, workflowHash, t4head);
|
||||
|
||||
// afterMs in middle of range to exclude _t1
|
||||
const afterMs = Date.UTC(2026, 4, 20, 12, 0, 0);
|
||||
const result = await cmdThreadList(tmpDir, null, afterMs, null, null, null);
|
||||
|
||||
// Expected: t2 (idle), t3 (running), _t5 (idle); excludes t4 (completed) and _t1 (filtered by time)
|
||||
expect(result).toHaveLength(3);
|
||||
const ids = result.map((r) => r.thread).sort();
|
||||
expect(ids).toEqual([t2, t3, _t5].sort());
|
||||
|
||||
await deleteMarker(tmpDir, t3);
|
||||
});
|
||||
|
||||
test("default + pagination composes correctly", async () => {
|
||||
const uwf = await makeUwfStore(tmpDir);
|
||||
const workflowHash = await createTestWorkflow(uwf);
|
||||
|
||||
// Create 10 idle threads + 5 completed threads
|
||||
const idleThreads: ThreadId[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
idleThreads.push(
|
||||
await createTestThread(uwf, tmpDir, workflowHash, Date.now() - (15 - i) * 1000),
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const t = await createTestThread(uwf, tmpDir, workflowHash, Date.now() - (5 - i) * 1000);
|
||||
const uwfIdx = await createUwfStore(tmpDir);
|
||||
const idx = loadAllThreads(uwfIdx.varStore);
|
||||
const head = idx[t]!.head;
|
||||
if (head === undefined) throw new Error("head not found");
|
||||
await completeThread(tmpDir, t, workflowHash, head);
|
||||
}
|
||||
|
||||
const result = await cmdThreadList(tmpDir, null, null, null, 2, 3);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
// All results should be idle (default excludes completed)
|
||||
for (const r of result) {
|
||||
expect(r.status).toBe("idle");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ describe("suspended thread display", () => {
|
||||
[idleThreadId]: idleEntry,
|
||||
});
|
||||
|
||||
// Test thread list
|
||||
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
|
||||
// Test thread list — pass showAll=true to include suspended threads
|
||||
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null, true);
|
||||
|
||||
// Find the suspended and idle threads in results
|
||||
const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
|
||||
|
||||
@@ -233,11 +233,12 @@ function parsePaginationOptions(
|
||||
|
||||
thread
|
||||
.command("list")
|
||||
.description("List threads")
|
||||
.description("List threads (defaults to active: idle + running)")
|
||||
.option(
|
||||
"--status <status>",
|
||||
"Filter by status: idle, running, completed, cancelled, active (idle+running), or comma-separated values",
|
||||
)
|
||||
.option("--all", "Show all threads regardless of status (overrides default active-only filter)")
|
||||
.option("--after <date>", "Filter threads created after this date (ISO or relative like '7d')")
|
||||
.option("--before <date>", "Filter threads created before this date (ISO or relative like '7d')")
|
||||
.option("--skip <n>", "Skip first n threads")
|
||||
@@ -245,6 +246,7 @@ thread
|
||||
.action(
|
||||
(opts: {
|
||||
status: string | undefined;
|
||||
all: boolean | undefined;
|
||||
after: string | undefined;
|
||||
before: string | undefined;
|
||||
skip: string | undefined;
|
||||
@@ -256,6 +258,7 @@ thread
|
||||
const nowMs = Date.now();
|
||||
const { afterMs, beforeMs } = parseTimeFilters(opts.after, opts.before, nowMs);
|
||||
const { skip, take } = parsePaginationOptions(opts.skip, opts.take);
|
||||
const showAll = opts.all === true;
|
||||
|
||||
const result = await cmdThreadList(
|
||||
storageRoot,
|
||||
@@ -264,6 +267,7 @@ thread
|
||||
beforeMs,
|
||||
skip,
|
||||
take,
|
||||
showAll,
|
||||
);
|
||||
writeOutput(result);
|
||||
});
|
||||
|
||||
@@ -650,18 +650,25 @@ export async function cmdThreadList(
|
||||
beforeMs: number | null,
|
||||
skip: number | null,
|
||||
take: number | null,
|
||||
showAll: boolean = false,
|
||||
): Promise<ThreadListItemWithStatus[]> {
|
||||
const uwf = await createUwfStore(storageRoot);
|
||||
const index = loadActiveThreads(uwf.varStore);
|
||||
|
||||
// Resolve the effective filter:
|
||||
// - explicit --status wins (showAll has no effect)
|
||||
// - otherwise: --all → no filter; default → ["idle", "running"]
|
||||
const effectiveFilter: ThreadStatus[] | null =
|
||||
statusFilter !== null ? statusFilter : showAll ? null : ["idle", "running"];
|
||||
|
||||
// Collect active threads
|
||||
let items = await collectActiveThreads(storageRoot, uwf, index);
|
||||
|
||||
// Collect completed threads (if relevant for status filter)
|
||||
const includeCompleted =
|
||||
statusFilter === null ||
|
||||
statusFilter.includes("completed") ||
|
||||
statusFilter.includes("cancelled");
|
||||
effectiveFilter === null ||
|
||||
effectiveFilter.includes("completed") ||
|
||||
effectiveFilter.includes("cancelled");
|
||||
if (includeCompleted) {
|
||||
const activeIds = new Set(items.map((i) => i.thread));
|
||||
const completedItems = collectCompletedThreads(uwf, activeIds);
|
||||
@@ -669,8 +676,8 @@ export async function cmdThreadList(
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if (statusFilter !== null) {
|
||||
items = items.filter((item) => statusFilter.includes(item.status));
|
||||
if (effectiveFilter !== null) {
|
||||
items = items.filter((item) => effectiveFilter.includes(item.status));
|
||||
}
|
||||
|
||||
// Apply time range filters
|
||||
|
||||
@@ -29,8 +29,9 @@ uwf thread exec <thread-id> # execute one moderator→agen
|
||||
[-c, --count <number>] # run multiple steps (default: 1)
|
||||
[--background] # run in background
|
||||
uwf thread show <thread-id> # show thread head pointer
|
||||
uwf thread list # list threads
|
||||
[--status <status>] # filter: idle, running, or completed
|
||||
uwf thread list # list active threads (idle + running)
|
||||
[--all] # include completed/cancelled/suspended
|
||||
[--status <status>] # filter: idle, running, suspended, completed, cancelled, active
|
||||
uwf thread read <thread-id> # render thread context as markdown
|
||||
[--quota <chars>] # max output characters (default 32000)
|
||||
[--before <step-hash>] # load steps before this hash (exclusive)
|
||||
|
||||
@@ -67,8 +67,9 @@ uwf thread exec <thread-id> # execute one step
|
||||
[-c, --count <n>] # run n steps
|
||||
[--background] # run in background
|
||||
uwf thread show <thread-id> # show head pointer
|
||||
uwf thread list # list all threads
|
||||
[--status <filter>] # idle, running, completed, cancelled, active (comma-separated)
|
||||
uwf thread list # list active threads (idle + running)
|
||||
[--all] # include completed/cancelled/suspended
|
||||
[--status <filter>] # idle, running, suspended, completed, cancelled, active (comma-separated)
|
||||
[--after <thread-id>] # pagination: after this thread
|
||||
[--before <thread-id>] # pagination: before this thread
|
||||
[--skip <n>] # skip first n results
|
||||
|
||||
Reference in New Issue
Block a user