Gateway: changed files, search across hosts, a richer complication summary, and question options in pushes
This commit is contained in:
1 parent
45251b1a43
commit
b08fd9bdb1
13 files changed
+500
-19
No files matched your search
@@ -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, currentActivity, loadHistory, projectThreads, buildSummary, routeKind, summarize, SummaryCache, trimText } from "../src/views.ts";
|
||||
import { agentProviders, buildFeed, currentActivity, latestChanges, searchThreads, loadHistory, projectThreads, buildSummary, routeKind, summarize, SummaryCache, trimText } from "../src/views.ts";
|
||||
|
||||
const thread = (overrides: Partial<ThreadShell> = {}): ThreadShell => ({
|
||||
id: "thread-1",
|
||||
@@ -107,10 +107,16 @@ describe("summarize", () => {
|
||||
describe("buildSummary", () => {
|
||||
const at = (minute: number) => `2026-11-01T09:${String(minute).padStart(2, "0")}:00.000Z`;
|
||||
const host = (label: string, status: string, threads: ThreadShell[]) =>
|
||||
({ id: label, label, status, threads: new Map(threads.map((t) => [t.id, t])) }) as unknown as T3Host;
|
||||
({
|
||||
id: label,
|
||||
label,
|
||||
status,
|
||||
threads: new Map(threads.map((t) => [t.id, t])),
|
||||
bounded: () => Promise.reject(new Error("not read")),
|
||||
}) as unknown as T3Host;
|
||||
|
||||
it("counts waiting and working threads and names the one waiting longest", () => {
|
||||
const summary = buildSummary([
|
||||
it("counts waiting and working threads and names the one waiting longest", async () => {
|
||||
const summary = await buildSummary([
|
||||
host("Mac mini", "online", [
|
||||
thread({ id: "a", title: "Newer ask", pendingRuntimeRequest: { id: "r1", kind: "command", createdAt: at(30) } }),
|
||||
thread({ id: "b", status: "running" }),
|
||||
@@ -120,11 +126,12 @@ describe("buildSummary", () => {
|
||||
host("LittleMan", "offline", [
|
||||
thread({ id: "e", title: "Older ask", pendingRuntimeRequest: { id: "q1", kind: "user_input", createdAt: at(5) } }),
|
||||
]),
|
||||
]);
|
||||
], new SummaryCache());
|
||||
expect(summary).toEqual({
|
||||
needsYou: 2,
|
||||
working: 1,
|
||||
next: { hostId: "LittleMan", threadId: "e", title: "Older ask", hostLabel: "LittleMan" },
|
||||
next: { hostId: "LittleMan", threadId: "e", title: "Older ask", hostLabel: "LittleMan", request: null },
|
||||
current: { hostId: "Mac mini", threadId: "b", title: "Receipt OCR", activity: null },
|
||||
hostsOffline: 1,
|
||||
});
|
||||
});
|
||||
@@ -286,3 +293,53 @@ describe("projectThreads", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("latestChanges", () => {
|
||||
it("sums each file the latest run changed, most lines first", () => {
|
||||
const change = (runId: string, fileName: string, additions: number, deletions: number) =>
|
||||
({ type: "file_change", runId, fileName, additions, deletions });
|
||||
expect(
|
||||
latestChanges([
|
||||
change("run-1", "old.ts", 50, 0),
|
||||
change("run-2", "gateway/compose.yml", 3, 2),
|
||||
change("run-2", "gateway/Containerfile", 31, 0),
|
||||
change("run-2", "gateway/compose.yml", 6, 4),
|
||||
{ type: "assistant_message", runId: "run-2" },
|
||||
]),
|
||||
).toEqual([
|
||||
{ path: "gateway/Containerfile", name: "Containerfile", additions: 31, deletions: 0 },
|
||||
{ path: "gateway/compose.yml", name: "compose.yml", additions: 9, deletions: 6 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("searchThreads", () => {
|
||||
it("merges every host's hits newest first and names hosts it could not search", async () => {
|
||||
const searching = (label: string, status: string, threads: ThreadShell[], matches: object[]) =>
|
||||
({
|
||||
id: label,
|
||||
label,
|
||||
status,
|
||||
projects: new Map([["project-1", { title: "wristcode" }]]),
|
||||
threads: new Map(threads.map((t) => [t.id, t])),
|
||||
search: () => Promise.resolve(matches),
|
||||
}) as unknown as T3Host;
|
||||
const match = (threadId: string, snippet: string, at: string) => ({ threadId, projectId: "project-1", source: "assistant", snippet, messageCreatedAt: at });
|
||||
const result = await searchThreads(
|
||||
[
|
||||
searching("Desktop", "online", [thread({ id: "a", title: "Gateway Containerfile" })], [match("a", "Gateway Containerfile", "2026-11-01T08:00:00.000Z")]),
|
||||
searching("Mac mini", "online", [thread({ id: "b", title: "Jellyfin" }), thread({ id: "gone", deletedAt: "x" })], [
|
||||
match("b", "rebuild from the Containerfile", "2026-11-01T09:00:00.000Z"),
|
||||
match("gone", "Containerfile", "2026-11-01T10:00:00.000Z"),
|
||||
]),
|
||||
searching("Laptop", "offline", [], []),
|
||||
],
|
||||
"containerfile",
|
||||
);
|
||||
expect(result.hits.map((hit) => [hit.hostLabel, hit.thread.threadId, hit.snippet])).toEqual([
|
||||
["Mac mini", "b", "rebuild from the Containerfile"],
|
||||
["Desktop", "a", null],
|
||||
]);
|
||||
expect(result.unsearched).toEqual(["Laptop"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user