Gateway: activity line, thread history paging, a project's threads, and a thread's subagents
Gateway / check (push) Successful in 9s
Gateway / image (push) Successful in 21s

This commit is contained in:
gib committed 2026-11-02 12:42:44 -05:00
1 parent 6bc9cac6ca
commit 52210d0223
11 files changed
+474 -19

No files matched your search

+56 -1
View File
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import type { BoundedThread, ThreadShell } from "../src/t3/types.ts";
import type { T3Host } from "../src/t3/host.ts";
import { agentProviders, buildFeed, buildSummary, routeKind, summarize, SummaryCache, trimText } from "../src/views.ts";
import { agentProviders, buildFeed, currentActivity, loadHistory, projectThreads, buildSummary, routeKind, summarize, SummaryCache, trimText } from "../src/views.ts";
const thread = (overrides: Partial<ThreadShell> = {}): ThreadShell => ({
id: "thread-1",
@@ -231,3 +231,58 @@ describe("agentProviders", () => {
]);
});
});
describe("currentActivity", () => {
const item = (type: string, status: string, extra: object = {}) => ({ type, status, startedAt: "2026-11-01T09:00:00.000Z", ...extra });
it("says the newest step still in progress, leaving out monitors", () => {
expect(
currentActivity([
item("file_change", "running", { fileName: "gateway/src/host.ts" }),
item("command_execution", "running", { input: "bun test" }),
item("command_execution", "running", { input: "tail -f log", monitor: true }),
item("assistant_message", "running"),
]),
).toEqual({ kind: "command", text: "bun test", startedAt: "2026-11-01T09:00:00.000Z" });
expect(currentActivity([item("file_change", "running", { fileName: "gateway/src/host.ts" })])?.text).toBe("host.ts");
});
it("is null once every step finished", () => {
expect(currentActivity([item("command_execution", "completed", { input: "bun test" })])).toBeNull();
});
});
describe("thread history", () => {
const messages = Array.from({ length: 45 }, (_, i) => message(i % 2 ? "assistant" : "user", `m${i}`));
const window = { snapshotSequence: 1, historyCursor: "t3-cursor", hasMoreHistory: true, projection: { thread: thread(), messages, turnItems: [] } };
const host = { bounded: () => Promise.resolve(window) } as unknown as T3Host;
it("pages back through the bounded window, then hands over to T3's cursor", async () => {
expect(summarize(window as BoundedThread, thread()).earlierCursor).toBe("w25");
const first = await loadHistory(host, thread(), "w25");
expect([first.messages[0]?.text, first.messages.at(-1)?.text, first.earlierCursor]).toEqual(["m5", "m24", "w5"]);
const second = await loadHistory(host, thread(), "w5");
expect([second.messages.length, second.earlierCursor]).toEqual([5, "ht3-cursor"]);
});
});
describe("projectThreads", () => {
it("lists a project's threads newest first, settled ones too, without subagents", () => {
const host = {
id: "Desktop",
threads: new Map(
[
thread({ id: "old", updatedAt: "2026-11-01T08:00:00.000Z", settledOverride: "settled", status: "completed" }),
thread({ id: "new", updatedAt: "2026-11-01T10:00:00.000Z" }),
thread({ id: "sub", lineage: { relationshipToParent: "subagent", parentThreadId: "new" } }),
thread({ id: "gone", archivedAt: "2026-11-01T09:30:00.000Z" }),
thread({ id: "elsewhere", projectId: "project-2" }),
].map((t) => [t.id, t]),
),
} as unknown as T3Host;
expect(projectThreads(host, "project-1").map((row) => [row.threadId, row.phase, row.settled])).toEqual([
["new", "running", false],
["old", "completed", true],
]);
});
});