Merge pull request 'fix(cli): thread list defaults to active threads only' (#159) from fix/147-thread-list-active-default into main
CI / check (push) Successful in 2m51s
CI / check (push) Successful in 2m51s
This commit was merged in pull request #159.
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
"@united-workforce/cli": minor
|
||||||
|
"@united-workforce/util": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(cli): `uwf thread list` now defaults to active threads only
|
||||||
|
|
||||||
|
Changes the default behavior of `uwf thread list` to show only active threads
|
||||||
|
(idle + running). Adds a new `--all` flag to opt into the previous behavior of
|
||||||
|
listing every thread (including completed, cancelled, and suspended).
|
||||||
|
|
||||||
|
When invoked with no flags, the command now hides completed/cancelled/suspended
|
||||||
|
threads. Use `--all` to see them, or `--status <status>` to filter explicitly.
|
||||||
|
The `--status` filter wins when both are present. Resolves issue #147.
|
||||||
@@ -49,7 +49,7 @@ bun link packages/cli
|
|||||||
| `uwf thread start <workflow> -p <prompt>` | Create a thread without executing |
|
| `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 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 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 |
|
| `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.
|
`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
|
||||||
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV -c 3 --agent uwf-builtin
|
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV -c 3 --agent uwf-builtin
|
||||||
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV --background
|
uwf thread exec 01ARZ3NDEKTSV4RRFFQ69G5FAV --background
|
||||||
|
uwf thread list
|
||||||
|
uwf thread list --all
|
||||||
uwf thread list --status running
|
uwf thread list --status running
|
||||||
uwf thread list --status active
|
uwf thread list --status active
|
||||||
uwf thread list --status idle,completed
|
uwf thread list --status idle,completed
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ describe("currentRole field", () => {
|
|||||||
const _compHead = loadActiveThreads(uwfForIndex.varStore)[compId]!.head;
|
const _compHead = loadActiveThreads(uwfForIndex.varStore)[compId]!.head;
|
||||||
completeThread(uwfForIndex.varStore, compId, "completed");
|
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);
|
const idleItem = list.find((i) => i.thread === idleId);
|
||||||
expect(idleItem).toBeDefined();
|
expect(idleItem).toBeDefined();
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ describe("cmdThreadList status filter", () => {
|
|||||||
expect(result[0]?.status).toBe("completed");
|
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 uwf = await makeUwfStore(tmpDir);
|
||||||
const workflowHash = await createTestWorkflow(uwf);
|
const workflowHash = await createTestWorkflow(uwf);
|
||||||
|
|
||||||
@@ -185,8 +185,290 @@ describe("cmdThreadList status filter", () => {
|
|||||||
|
|
||||||
const result = await cmdThreadList(tmpDir, null, null, null, null, null);
|
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).toHaveLength(3);
|
||||||
expect(result.map((r) => r.thread).sort()).toEqual([thread1, thread2, thread3].sort());
|
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,
|
[idleThreadId]: idleEntry,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test thread list
|
// Test thread list — pass showAll=true to include suspended threads
|
||||||
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null);
|
const listResult = await cmdThreadList(tmpDir, null, null, null, null, null, true);
|
||||||
|
|
||||||
// Find the suspended and idle threads in results
|
// Find the suspended and idle threads in results
|
||||||
const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
|
const suspendedItem = listResult.find((item) => item.thread === suspendedThreadId);
|
||||||
|
|||||||
@@ -233,11 +233,12 @@ function parsePaginationOptions(
|
|||||||
|
|
||||||
thread
|
thread
|
||||||
.command("list")
|
.command("list")
|
||||||
.description("List threads")
|
.description("List threads (defaults to active: idle + running)")
|
||||||
.option(
|
.option(
|
||||||
"--status <status>",
|
"--status <status>",
|
||||||
"Filter by status: idle, running, completed, cancelled, active (idle+running), or comma-separated values",
|
"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("--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("--before <date>", "Filter threads created before this date (ISO or relative like '7d')")
|
||||||
.option("--skip <n>", "Skip first n threads")
|
.option("--skip <n>", "Skip first n threads")
|
||||||
@@ -245,6 +246,7 @@ thread
|
|||||||
.action(
|
.action(
|
||||||
(opts: {
|
(opts: {
|
||||||
status: string | undefined;
|
status: string | undefined;
|
||||||
|
all: boolean | undefined;
|
||||||
after: string | undefined;
|
after: string | undefined;
|
||||||
before: string | undefined;
|
before: string | undefined;
|
||||||
skip: string | undefined;
|
skip: string | undefined;
|
||||||
@@ -256,6 +258,7 @@ thread
|
|||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
const { afterMs, beforeMs } = parseTimeFilters(opts.after, opts.before, nowMs);
|
const { afterMs, beforeMs } = parseTimeFilters(opts.after, opts.before, nowMs);
|
||||||
const { skip, take } = parsePaginationOptions(opts.skip, opts.take);
|
const { skip, take } = parsePaginationOptions(opts.skip, opts.take);
|
||||||
|
const showAll = opts.all === true;
|
||||||
|
|
||||||
const result = await cmdThreadList(
|
const result = await cmdThreadList(
|
||||||
storageRoot,
|
storageRoot,
|
||||||
@@ -264,6 +267,7 @@ thread
|
|||||||
beforeMs,
|
beforeMs,
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
|
showAll,
|
||||||
);
|
);
|
||||||
writeOutput(result);
|
writeOutput(result);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -650,18 +650,25 @@ export async function cmdThreadList(
|
|||||||
beforeMs: number | null,
|
beforeMs: number | null,
|
||||||
skip: number | null,
|
skip: number | null,
|
||||||
take: number | null,
|
take: number | null,
|
||||||
|
showAll: boolean = false,
|
||||||
): Promise<ThreadListItemWithStatus[]> {
|
): Promise<ThreadListItemWithStatus[]> {
|
||||||
const uwf = await createUwfStore(storageRoot);
|
const uwf = await createUwfStore(storageRoot);
|
||||||
const index = loadActiveThreads(uwf.varStore);
|
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
|
// Collect active threads
|
||||||
let items = await collectActiveThreads(storageRoot, uwf, index);
|
let items = await collectActiveThreads(storageRoot, uwf, index);
|
||||||
|
|
||||||
// Collect completed threads (if relevant for status filter)
|
// Collect completed threads (if relevant for status filter)
|
||||||
const includeCompleted =
|
const includeCompleted =
|
||||||
statusFilter === null ||
|
effectiveFilter === null ||
|
||||||
statusFilter.includes("completed") ||
|
effectiveFilter.includes("completed") ||
|
||||||
statusFilter.includes("cancelled");
|
effectiveFilter.includes("cancelled");
|
||||||
if (includeCompleted) {
|
if (includeCompleted) {
|
||||||
const activeIds = new Set(items.map((i) => i.thread));
|
const activeIds = new Set(items.map((i) => i.thread));
|
||||||
const completedItems = collectCompletedThreads(uwf, activeIds);
|
const completedItems = collectCompletedThreads(uwf, activeIds);
|
||||||
@@ -669,8 +676,8 @@ export async function cmdThreadList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply status filter
|
// Apply status filter
|
||||||
if (statusFilter !== null) {
|
if (effectiveFilter !== null) {
|
||||||
items = items.filter((item) => statusFilter.includes(item.status));
|
items = items.filter((item) => effectiveFilter.includes(item.status));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply time range filters
|
// 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)
|
[-c, --count <number>] # run multiple steps (default: 1)
|
||||||
[--background] # run in background
|
[--background] # run in background
|
||||||
uwf thread show <thread-id> # show thread head pointer
|
uwf thread show <thread-id> # show thread head pointer
|
||||||
uwf thread list # list threads
|
uwf thread list # list active threads (idle + running)
|
||||||
[--status <status>] # filter: idle, running, or completed
|
[--all] # include completed/cancelled/suspended
|
||||||
|
[--status <status>] # filter: idle, running, suspended, completed, cancelled, active
|
||||||
uwf thread read <thread-id> # render thread context as markdown
|
uwf thread read <thread-id> # render thread context as markdown
|
||||||
[--quota <chars>] # max output characters (default 32000)
|
[--quota <chars>] # max output characters (default 32000)
|
||||||
[--before <step-hash>] # load steps before this hash (exclusive)
|
[--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
|
[-c, --count <n>] # run n steps
|
||||||
[--background] # run in background
|
[--background] # run in background
|
||||||
uwf thread show <thread-id> # show head pointer
|
uwf thread show <thread-id> # show head pointer
|
||||||
uwf thread list # list all threads
|
uwf thread list # list active threads (idle + running)
|
||||||
[--status <filter>] # idle, running, completed, cancelled, active (comma-separated)
|
[--all] # include completed/cancelled/suspended
|
||||||
|
[--status <filter>] # idle, running, suspended, completed, cancelled, active (comma-separated)
|
||||||
[--after <thread-id>] # pagination: after this thread
|
[--after <thread-id>] # pagination: after this thread
|
||||||
[--before <thread-id>] # pagination: before this thread
|
[--before <thread-id>] # pagination: before this thread
|
||||||
[--skip <n>] # skip first n results
|
[--skip <n>] # skip first n results
|
||||||
|
|||||||
Reference in New Issue
Block a user